package com.quwan.component;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;
import com.quwan.app.vo.pay.ApplePayResult;
import com.quwan.common.exception.ResponseException;
import com.quwan.common.result.ApiResult;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.micrometer.core.instrument.util.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import javax.net.ssl.*;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.net.URL;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.security.spec.RSAPublicKeySpec;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;

/**
 * @author quan
 * @date 2021-12-08 15:51
 */
@Slf4j
@Component
public class AppleComponent {
    /**
     * 获取公钥路径
     */
    @Value("${apple.publicKeyUrl}")
    private String publicKeyUrl;
    @Value("${apple.issuer}")
    private String issuer;
    @Value("${apple.appleAud}")
    private String appleAud;
    @Autowired
    private Environment environment;
    @Autowired
    private RestTemplate restTemplate;


    public String authToken(String token) throws Exception {
        // 解析
        Map<String, JSONObject> json = _parserToken(token);
        JSONObject header = json.get("header");
        String kid = header.getString("kid");
        // 获取公钥
        PublicKey publicKey = _publicKey(kid);
        if (publicKey == null)
            throw new ResponseException(500, "apple 公钥获取失败");
        // https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens
        JwtParser jwtParser = Jwts.parser().requireIssuer(issuer).requireAudience(appleAud).setSigningKey(publicKey);
        Jws<Claims> claim = jwtParser.parseClaimsJws(token);
        if (claim != null) {
            String sub = claim.getBody().get("sub").toString();//sub,即用户的Apple的openId
            String iss = claim.getBody().get("iss").toString();
            String aud = claim.getBody().get("aud").toString();
            Long exp = Long.valueOf(claim.getBody().get("exp").toString()) * 1000;//exp is second
            if (issuer.equals(iss)
                    && appleAud.equals(aud)
                    && System.currentTimeMillis() < exp) {
                return sub;
            } else
                throw new ResponseException(400, "appleToken 不合法");
        }
        return null;
    }

    private Map<String, JSONObject> _parserToken(String token) {
        Map<String, JSONObject> map = Maps.newHashMap();
        String[] arr = token.split("\\.");

        String decodeHeader = new String(Base64.decodeBase64(arr[0]));
        JSONObject header = JSON.parseObject(decodeHeader);
        map.put("header", header);

        String decodePayload = new String(Base64.decodeBase64(arr[1]));
        JSONObject payload = JSON.parseObject(decodePayload);
        map.put("payload", payload);
        return map;
    }


    private PublicKey _publicKey(String kid) throws Exception {
        Map<String, Map<String, String>> map = new HashMap<>();
        ResponseEntity<String> response = restTemplate.getForEntity(publicKeyUrl, String.class);
        if (!response.getStatusCode().equals(HttpStatus.OK))
            throw new ResponseException(500, "请求发起失败");
        else if (StringUtils.isBlank(response.getBody()))
            throw new ResponseException(500, "apple 公钥获取失败");

        log.info("response::{}", response.getBody());
        JSONObject res = JSONObject.parseObject(response.getBody());
        JSONArray jsonArray = res.getJSONArray("keys");
        String kty = "";
        for (int i = 0; i < jsonArray.size(); i++) {
            kty = jsonArray.getJSONObject(i).getString("kty");
            Map<String, String> m = new HashMap<String, String>();
            m.put("n", jsonArray.getJSONObject(i).getString("n"));
            m.put("e", jsonArray.getJSONObject(i).getString("e"));
            map.put(jsonArray.getJSONObject(i).getString("kid"), m);
        }

        String n = map.get(kid).get("n");
        String e = map.get(kid).get("e");
        BigInteger modulus = new BigInteger(1, Base64.decodeBase64(n));
        BigInteger publicExponent = new BigInteger(1, Base64.decodeBase64(e));
        RSAPublicKeySpec rsaPublicKeySpec = new RSAPublicKeySpec(modulus, publicExponent);
        KeyFactory factory = KeyFactory.getInstance(kty);
        return factory.generatePublic(rsaPublicKeySpec);

    }


    // IOS内购 - 沙箱环境
    private static final String URL_SANDBOX = "https://sandbox.itunes.apple.com/verifyReceipt";
    // IOS内购 - 线上环境
    private static final String URL_VERIFY = "https://buy.itunes.apple.com/verifyReceipt";

   private String  CODE_SUCCESS = "0";// 收据是有效, 验证成功
   private String  CODE_NULL = "-1";// 苹果服务器没有返回验证结果
   private String  CODE_21000="21000";// 没有使用HTTP POST请求方法向App Store发出请求
   private String  CODE_21001="21001";// 这个状态码不再由App Store发送
   private String  CODE_21002="21002";// receipt-data属性中的数据格式错误或服务遇到临时问题
   private String  CODE_21003="21003";// 这张收据无法证实真伪
   private String  CODE_21004="21004";// 您提供的共享秘密与您的帐户文件中的共享秘密不匹配
   private String  CODE_21005="21005";// 收据服务器暂时无法提供收据
   private String  CODE_21006="21006";// 订单是有效的，但订阅服务已经过期。当收到这个信息时，解码后的收据信息也包含在返回内容中
   private String  CODE_21007="21007";// 这个收据来自测试环境，但是它被发送到生产环境进行验证
   private String  CODE_21008="21008";// 这个收据来自生产环境，但是它被发送到测试环境进行验证
   private String  CODE_21009="21009";// 内部数据访问错误
   private String  CODE_21010="21010";// 用户帐户找不到或已被删除


    private static class TrustAnyTrustManager implements X509TrustManager {

        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[]{};
        }
    }

    private static class TrustAnyHostnameVerifier implements HostnameVerifier {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    }

    /**
     * 苹果支付验证（接口返回null则未查询到收据信息）
     *
     * @param receipt       // 收据
     * @param transactionId // 交易的唯一标识符
     * @return
     */
    public ApplePayResult applePayVerify(String receipt, String transactionId) {

        String  url = environment.getActiveProfiles()[0].equalsIgnoreCase("prod") ? URL_VERIFY : URL_SANDBOX;

        String verifyResult = buyAppVerify(receipt, url);
        JSONObject resultJob = JSONObject.parseObject(verifyResult);
        String status = resultJob.getString("status"); // 苹果验证返回结果状态码
        /**
         * 注 ：
         *  此处写法原因 ：项目上线的时候，环境肯定为生产环境，正常也是走苹果线上环境，但是因为上线至 App Store 需要苹果公司人员审核，
         *     他们支付的时候使用的是沙箱支付，如没有此处代码，支付将会失败，上线也会被打回，重新提交审核
         */
        if (CODE_21007.equals(status)) {
            verifyResult = buyAppVerify(receipt, URL_SANDBOX);
            resultJob = JSONObject.parseObject(verifyResult);
            status = resultJob.getString("status");
        }
        if (!CODE_SUCCESS.equals(status)) {
            log.info("【苹果服务器验证失败】返回验证结果。 transactionId: {}, status: {}", transactionId, status);
            return  new ApplePayResult(false,"苹果服务器验证失败",null);
        }
        String resultReceipt = resultJob.getString("receipt");
        JSONObject receiptJson = JSONObject.parseObject(resultReceipt);
        String in_app = receiptJson.getString("in_app"); // 苹果收据列表
        if (StringUtils.isEmpty(in_app)) {
            log.error("【苹果服务器验证失败】未查询到收据信息。 transactionId: {}, status: {}", transactionId, status);
            return  new ApplePayResult(false,"未查询到收据信息",null);
        }
        Optional<JSONObject> ret = JSONArray.parseArray(in_app)
                .stream()
                .map(object -> JSONObject.parseObject(object.toString()))
                .filter(f -> transactionId.equalsIgnoreCase(f.getString("transaction_id"))).findFirst();

        if (!ret.isPresent())
            return  new ApplePayResult(false,"当前交易信息不在订单交易数据列表",null);

         return  new ApplePayResult(true,"当前交易信息不在订单交易数据列表",ret.get());

    }

    /**
     * 苹果服务器验证
     *
     * @param receipt 账单
     * @param url     IOS内购核验环境地址
     * @return null 或返回结果
     * @url 要验证的地址
     */
    private String buyAppVerify(String receipt, String url) {
        try {
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, new TrustManager[]{new TrustAnyTrustManager()}, new java.security.SecureRandom());
            URL console = new URL(url);
            HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();
            conn.setSSLSocketFactory(sc.getSocketFactory());
            conn.setHostnameVerifier(new TrustAnyHostnameVerifier());
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Content-Type", "application/json");
            conn.setRequestProperty("Proxy-Connection", "Keep-Alive");
            conn.setDoInput(true);
            conn.setDoOutput(true);
            BufferedOutputStream hurlBufOus = new BufferedOutputStream(conn.getOutputStream());
            // 拼成固定的格式传给平台
            String str = String.format(Locale.CHINA, "{\"receipt-data\":\"" + receipt + "\"}");
            hurlBufOus.write(str.getBytes());
            hurlBufOus.flush();

            InputStream is = conn.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(is));
            String line;
            StringBuffer sb = new StringBuffer();
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
            return sb.toString();
        } catch (Exception e) {
            log.error("苹果服务器异常 errMsg: {}", e);
            /**
             * 注 ：
             *  此处业务异常类私有不对外，各位可根据个人业务进行处理
             */
            throw new ResponseException(500, "苹果服务器异常");
        }
    }
}

